package org.jaaksi.recorddemo;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 支持录制的格式：16比特量化的wav格式
 */
public class AudioMessageRecord {
  private static final String TAG = "MessageRecord";
  // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
  private final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
  private final ExecutorService executor = Executors.newCachedThreadPool();
  // 音频获取源
  private int audioSource = MediaRecorder.AudioSource.MIC; //只有系统app才能录制VOICE_CALL
  // 设置音频采样率，44100是目前的标准，但是某些设备仍然支持22050，16000，11025
  private int sampleRate = 8000;
  // 设置音频的录制的声道:CHANNEL_IN_STEREO 为双声道，CHANNEL_IN_MONO 为单声道
  private int channelConfig;

  // 缓冲区字节大小
  private int channels = 1;
  private int bufferSize = 0;
  private boolean isRecord = false;// 设置正在录制的状态
  private String outFilePath;
  private String pcmFilePath;
  private AudioRecord audioRecord;

  public AudioMessageRecord() {
  }

  /**
   * @param channels 1单声道，2双声道
   */
  public void setChannels(int channels) {
    this.channels = channels;
  }

  /**
   * @param outFilePath 输出文件名（全路径）,以.wav结束
   */
  public void setOutFilePath(String outFilePath) {
    this.outFilePath = outFilePath;
    if (outFilePath == null || !outFilePath.endsWith(".wav")) {
      throw new IllegalArgumentException("filePath must endsWith .wav");
    }
    pcmFilePath = outFilePath.replace(".wav", ".pcm");
  }

  private void initAudioRecord() {
    channelConfig = channels == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO;
    // 获得缓冲区字节大小
    bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, AUDIO_FORMAT);
    // 创建AudioRecord对象
    audioRecord = new AudioRecord(audioSource, sampleRate,
        channelConfig, AUDIO_FORMAT, bufferSize);
  }

  public void startRecord() {
    if (audioRecord == null) initAudioRecord();
    audioRecord.startRecording();
    // 让录制状态为true
    isRecord = true;
    // 开启音频文件写入线程
    executor.execute(new AudioRecordThread());
  }

  public void stopRecord() {
    close();
  }

  private void close() {
    if (audioRecord != null) {
      System.out.println("stopRecord");
      isRecord = false;// 停止文件写入
      audioRecord.stop();
      audioRecord.release();// 释放资源
      audioRecord = null;
    }
  }

  private class AudioRecordThread implements Runnable {
    @Override
    public void run() {
      writeDateToFile();// 写入pcm
      transformWav();// 给裸数据加上头文件\WAV格式

      if (mAudioMessageRecordCallback != null) {
        mAudioMessageRecordCallback.audioRecordEnd();
      }
    }
  }

  private File checkFileExist(String filePath, boolean deleteIfExist) {
    if (filePath != null/* || !new File(filePath).isFile()*/) {
      File file = new File(pcmFilePath);
      if (deleteIfExist && file.exists()) {
        file.delete();
      }
      if (!file.getParentFile().exists()) {
        file.getParentFile().mkdirs();
      }
      try {
        file.createNewFile(); // 有的手机需要主动写文件
      } catch (IOException e) {
        e.printStackTrace();
      }
      return file;
    }

    return null;
  }

  private void writeDateToFile() {
    byte[] audioData = new byte[bufferSize];
    FileOutputStream fos = null;
    int readsize;
    try {
      File file = checkFileExist(pcmFilePath, true);
      fos = new FileOutputStream(file);
    } catch (Exception e) {
      e.printStackTrace();
    }
    if (fos == null) return;
    while (isRecord) {
      readsize = audioRecord.read(audioData, 0, bufferSize);
      if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
        try {
          fos.write(audioData);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    try {
      fos.close();// 关闭写入流
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void transformWav() { // 将PCM转换为wav
    FileInputStream in = null;
    FileOutputStream out = null;
    long pcmLen;
    long longSampleRate = sampleRate;
    //int channels = 1;
    long byteRate = 16 * sampleRate * channels / 8;

    //JNI增益处理必须是320
    byte[] data = new byte[bufferSize];
    try {
      in = new FileInputStream(pcmFilePath);
      out = new FileOutputStream(outFilePath);
      pcmLen = in.getChannel().size();
      WriteWaveFileHeader(out, pcmLen, longSampleRate, channels, byteRate);
      //音频会比视频晚一秒结束，经过试验，减去最后一秒的数据
      Log.i("count", "in.available() = " + in.available());
      int count = in.available() / 320;
      Log.i("count", "all count = " + count);
      while (in.read(data) != -1) {
        //if (count-- > 90) {
        // todo 通过JNI接口增益：降噪等
        //SettingsJni.retrieveSettings().kotiNsAndAgc(data);
        out.write(data);
        //}
      }
      in.close();
      out.close();
      File pcmFile = new File(pcmFilePath);
      //if (pcmFile.exists()){
      pcmFile.delete(); // 删除pcm文件
      //}
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (in != null) {
          in.close();
        }

        if (out != null) {
          out.flush();
          out.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * https://blog.csdn.net/xsjm206/article/details/6727023
   * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 为我为啥插入这44个字节，这个还真没深入研究，不过你随便打开一个wav
   * 音频的文件，可以发现前面的头文件可以说基本一样哦。每种格式的文件都有 自己特有的头文件。
   */
  private void WriteWaveFileHeader(FileOutputStream out, long pcmLen,
      long longSampleRate, int channels, long byteRate)
      throws IOException {
    long totalLen = pcmLen + 36;
    byte[] header = new byte[44];
    // 8KHz采样、16比特量化的线性PCM语音信号的WAVE文件头格式表（共44字节）
    // 8KHz采样、8比特A律量化的PCM语音信号的WAVE文件头格式表（共58字节）
    // 字节数 数据类型 内容 文件头定义为
    header[0] = 'R'; // RIFF/WAVE header
    header[1] = 'I';
    header[2] = 'F';
    header[3] = 'F';
    header[4] = (byte) (totalLen & 0xff); // 区别在于，这里都是用+36之后的计算，而
    header[5] = (byte) ((totalLen >> 8) & 0xff);
    header[6] = (byte) ((totalLen >> 16) & 0xff);
    header[7] = (byte) ((totalLen >> 24) & 0xff);
    header[8] = 'W';
    header[9] = 'A';
    header[10] = 'V';
    header[11] = 'E';
    header[12] = 'f'; // 'fmt ' chunk
    header[13] = 'm';
    header[14] = 't';
    header[15] = ' ';
    // 4 long int 10 00 00 00H(PCM) long int size1=0x10
    header[16] = 16;
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    // 2 int 01 00H int fmttag=0x01
    header[20] = 1; // format = 1
    header[21] = 0;
    // 2 int int channel=1 或2
    header[22] = (byte) channels;
    header[23] = 0;
    // 4 long int 采样率 long int samplespersec
    header[24] = (byte) (longSampleRate & 0xff);
    header[25] = (byte) ((longSampleRate >> 8) & 0xff);
    header[26] = (byte) ((longSampleRate >> 16) & 0xff);
    header[27] = (byte) ((longSampleRate >> 24) & 0xff);
    // 4 long int 每秒播放字节数 long int bytepersec
    header[28] = (byte) (byteRate & 0xff);
    header[29] = (byte) ((byteRate >> 8) & 0xff);
    header[30] = (byte) ((byteRate >> 16) & 0xff);
    header[31] = (byte) ((byteRate >> 24) & 0xff);
    // 2 int 采样一次占字节数 int blockalign=声道数*量化数/8
    header[32] = (byte) (channels * 16 / 8); // block align
    header[33] = 0;
    //  2 int 量化数 int bitpersamples
    header[34] = 16; // bits per sample
    header[35] = 0;
    //  4 char "data" char data_id="data"
    header[36] = 'd';
    header[37] = 'a';
    header[38] = 't';
    header[39] = 'a';
    // 4 long int 采样数据字节数 long int size2=文长-44
    header[40] = (byte) (pcmLen & 0xff);
    header[41] = (byte) ((pcmLen >> 8) & 0xff);
    header[42] = (byte) ((pcmLen >> 16) & 0xff);
    header[43] = (byte) ((pcmLen >> 24) & 0xff);
    out.write(header);
  }

  // 16BIT 格式兼容性更好
  // 单声道效率更高
  private int getAudioBufferSize(int channelLayout, int pcmFormat) {
    int bufferSize = 1024;

    switch (channelLayout) {
      case AudioFormat.CHANNEL_IN_MONO:
        bufferSize *= 1;
        break;
      case AudioFormat.CHANNEL_IN_STEREO:
        bufferSize *= 2;
        break;
    }

    switch (pcmFormat) {
      case AudioFormat.ENCODING_PCM_8BIT:
        bufferSize *= 1;
        break;
      case AudioFormat.ENCODING_PCM_16BIT:
        bufferSize *= 2;
        break;
    }

    return bufferSize;
  }

  AudioMessageRecordCallback mAudioMessageRecordCallback;

  public void setAudioCallback(AudioMessageRecordCallback audioMessageRecordCallback) {
    mAudioMessageRecordCallback = audioMessageRecordCallback;
  }

  public interface AudioMessageRecordCallback {
    /**
     * 录音结束
     */
    void audioRecordEnd();
  }
}
 
